/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.attitudes;

import ore.Attitude;
import ore.Frame;
import ore.PositionVelocity;
import ore.Transform;
import ore.errors.OrekitException;
import ore.time.AbsoluteDate;

import org.apache.commons.math.geometry.Rotation;
import org.apache.commons.math.geometry.Vector3D;

/**
 * This class handles ground pointing attitude laws.
 *
 * This class is a basic model for different kind of ground pointing
 * attitude laws, such as : body center pointing, nadir pointing,
 * target pointing, etc...
 * 
 * An instance of this class is immutable.
 * 
 * @author V&eacute;ronique Pommier-Maurussane
 */
public abstract class GroundPointing 
    extends Object
    implements ore.AttitudeLaw
{

    public final Frame bodyFrame;


    protected GroundPointing(Frame bodyFrame){
        super();
        this.bodyFrame = bodyFrame;
    }

    /** 
     * @return body frame
     */
    public Frame getBodyFrame() {
        return this.bodyFrame;
    }
    /** 
     * @param date date when the target point shall be computed
     * @param pv position/velocity of the point
     * @param frame frame in which the point shall be computed
     * 
     * @return Target point in body frame
     * 
     * @throws OrekitException if some specific error occurs,
     * such as no target reached
     */
    protected abstract PositionVelocity getTargetInBodyFrame(AbsoluteDate date,
                                                             PositionVelocity pv,
                                                             Frame frame)
        throws OrekitException;

    /** 
     * Compute the target ground point at given date in given frame.
     * 
     * Ensure that position/velocity and frame is consistent with
     * given frame.
     * 
     * @param date date when the point shall be computed
     * @param pv position-velocity of the point
     * @param frame frame in which the point shall be computed
     * 
     * @return Observed ground point position/velocity in given frame
     * 
     * @throws OrekitException if some specific error occurs
     */
    public PositionVelocity getObservedGroundPoint(final AbsoluteDate date,
                                                   final PositionVelocity pv,
                                                   final Frame frame)
        throws OrekitException
    {
        /*
         * Get target in body frame
         */
        PositionVelocity targetInBodyFrame = this.getTargetInBodyFrame(date, pv, frame);
        /*
         * Transform to given frame
         */
        Transform t = bodyFrame.getTransformTo(frame, date);
        /*
         * Target in given frame
         */
        return t.transformPositionVelocity(targetInBodyFrame);
    }
    /**
     * Compute the system state at given date in given frame.
     * 
     * Ensure that position/velocity and frame is consistent with
     * given frame.
     * 
     * @param date date when system state shall be computed
     * @param pv satellite position/velocity in given frame
     * @param frame the frame in which pv is defined
     * 
     * @return Satellite attitude state at given date in given frame.
     * 
     * @throws OrekitException if some specific error occurs
     */
    public Attitude getState(final AbsoluteDate date, final PositionVelocity pv, final Frame frame)
        throws OrekitException
    {
        /*
         * Construction of the satellite-target position/velocity vector
         */
        PositionVelocity pointing =  new PositionVelocity(1.0, 
                                                          this.getObservedGroundPoint(date, pv, frame),
                                                          -1.0, pv);
        Vector3D position = pointing.getPosition();
        Vector3D velocity = pointing.getVelocity();
        /*
         * New orekit exception if null position.
         */
        if (position.equals(Vector3D.ZERO))
            throw new OrekitException("satellite collided with target");
        else {
            /*
             * Attitude rotation in given frame :
             *  line of sight -> z satellite axis,
             *  satellite velocity -> x satellite axis.
             */
            Rotation rot = new Rotation(position, pv.getVelocity(), Vector3D.PLUS_K, Vector3D.PLUS_I);
            /*
             * Attitude spin
             */
            Vector3D spin = new Vector3D((1.0 / Vector3D.dotProduct(position, position)),
                                         Vector3D.crossProduct(position, velocity));

            return new Attitude(frame, rot, rot.applyTo(spin));
        }
    }
}
